﻿// -----------------------------------------------------------------------
// <copyright file="Unit.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;
    using System.Collections.Generic;


    /// <summary>
    ///   A business unit.
    /// </summary>
    /// <remarks>A business unit is a group of users that can be used
    /// to define the scope of security permissions. For instance,
    /// a table permission can be defined as to allow the selection of rows
    /// created by users of the same business unit.</remarks>
    public sealed class Unit : IIdentifiedById, IRemovable, IRefreshable
    {
        /// <summary>
        ///   The current domain.
        /// </summary>
        private readonly Domain _domain;

        /// <summary>
        ///   The name of the unit.
        /// </summary>
        private string _unitName;

        /// <summary>
        ///   The display name of the unit.
        /// </summary>
        private string _displayName;

        /// <summary>
        ///   Initializes a new instance of the <see cref="Centido.Core.Unit"/> class
        /// for the specified unit id and the specified domain.
        /// </summary>
        /// <param name = "unitId">The unit id.</param>
        /// <param name="unitUnitName">The unit name.</param>
        /// <param name = "domain">The current domain.</param>
        /// <exception cref="ArgumentNullException">THrown when the value of <paramref name="unitUnitName"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="domain"/> is null.</exception>
        internal Unit(Guid unitId, string unitUnitName, Domain domain)
        {
            if (unitUnitName == null)
                throw new ArgumentNullException("unitUnitName");
            if (domain == null)
                throw new ArgumentNullException("domain");

            Id = unitId;
            _unitName = unitUnitName;

            _domain = domain;
        }

        /// <summary>
        ///   Gets the current domain.
        /// </summary>
        public Domain Domain
        {
            get { return _domain; }
        }


        /// <summary>
        ///   Gets or sets the unit name.
        /// </summary>
        /// <remarks>
        /// <para>
        ///   Note that the unit name is trimmed and lowered.
        /// </para>
        /// <para>The current user has to have the permission to change security settings
        /// in order to set this property.</para> 
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to change the name and 
        /// a unit with the same name already exists.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <example>
        ///   The following example shows how to set the name of a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             unit.Name = "my_unit_2";
        /// 
        ///             Console.WriteLine("Unit name = {0}", unit.Name);
        /// 
        ///             //Creating another unit
        ///             var anotherUnit = domain.AddUnit("my_unit_3");
        /// 
        ///             try
        ///             {
        ///                 //Trying to set the unit name to the same value
        ///                 anotherUnit.Name = "my_unit_2";
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Unit names must be unique.");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Name
        {
            get
            {
                return _unitName ?? (_unitName = DbValue.GetString(
                    ReadProperty(Db.Tables.UnitList.Col.UnitName)));
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                value = value.Trim().ToLower();

                if (_unitName == value) return;

                WriteProperty(Db.Tables.UnitList.Col.UnitName, value, OdbcType.NVarChar);

                _unitName = value;
            }
        }


        /// <summary>
        ///   Gets or sets the unit display name.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings
        /// in order to set this property.</remarks>
        /// <exception cref="System.Data.Common">Thrown when the current user has no permission
        /// to set this property.</exception>
        /// <example>
        ///   The following example shows how to set the display name of a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             unit.DisplayName = "The display name of the unit";
        /// 
        ///             Console.WriteLine("Unit display name = {0}", unit.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string DisplayName
        {
            get { return _displayName ?? (_displayName = DbValue.GetString(
                ReadProperty(Db.Tables.UnitList.Col.DisplayName))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_displayName) && _displayName == value) return;

                WriteProperty(Db.Tables.UnitList.Col.DisplayName, value, OdbcType.NVarChar);

                _displayName = value;
            }
        }

        /// <summary>
        ///   Gets the parent units of the current unit.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the parent units of a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a child unit
        ///             var childUnit = domain.AddUnit("child_unit");
        /// 
        ///             //Creating two top units
        ///             var firstTopUnit = domain.AddUnit("first_top_unit");
        ///             firstTopUnit.DisplayName = "The first top unit";
        /// 
        ///             var secondTopUnit = domain.AddUnit("second_top_unit");
        ///             secondTopUnit.DisplayName = "The second top unit";
        /// 
        ///             firstTopUnit.AddUnit(childUnit);
        ///             secondTopUnit.AddUnit(childUnit);
        /// 
        ///             Console.WriteLine("Parent units:");
        /// 
        ///             foreach(var unit in childUnit.Parents)
        ///                 Console.WriteLine("\t{0}", unit.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Unit> Parents
        {
            get
            {
                var parentList = new List<Unit>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3} in (select {4} from {5} where {6}=?)",
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitList.Col.UnitName,
                            Domain.GetObjectFullName(Db.Tables.UnitList.Name),
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitParentList.Col.ParentUnitId,
                            Domain.GetObjectFullName(Db.Tables.UnitParentList.Name),
                            Db.Tables.UnitParentList.Col.UnitId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        // The parent unit id
                        var unitId = reader.GetGuid(Db.Tables.UnitList.Col.UnitId)
                            .GetValueOrDefault();

                        // The parent unit name
                        var unitName = reader.GetString(Db.Tables.UnitList.Col.UnitName);

                        parentList.Add(new Unit(unitId, unitName, Domain));
                    });

                return parentList;
            }
        }

        /// <summary>
        ///   Gets the child units of the current business unit.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the child units of a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a top unit
        ///             var topUnit = domain.AddUnit("top_unit");
        /// 
        ///             //Creating two child units
        ///             var firstChildUnit = domain.AddUnit("first_child_unit");
        ///             firstChildUnit.DisplayName = "The first child unit";
        /// 
        ///             var secondChildUnit = domain.AddUnit("second_child_unit");
        ///             secondChildUnit.DisplayName = "The second child unit";
        /// 
        ///             topUnit.AddUnit(firstChildUnit);
        ///             topUnit.AddUnit(secondChildUnit);
        /// 
        ///             Console.WriteLine("Child units:");
        /// 
        ///             foreach(var unit in topUnit.ChildUnits)
        ///                 Console.WriteLine("\t{0}", unit.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Unit> ChildUnits
        {
            get
            {
                var resultList = new List<Unit>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3} in (select {4} from {5} where {6}=?)",
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitList.Col.UnitName,
                            Domain.GetObjectFullName(Db.Tables.UnitList.Name),
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitParentList.Col.UnitId,
                            Domain.GetObjectFullName(Db.Tables.UnitParentList.Name),
                            Db.Tables.UnitParentList.Col.ParentUnitId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("parent_unit_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        // The unit id
                        var unitId = reader.GetGuid(Db.Tables.UnitList.Col.UnitId).GetValueOrDefault();

                        // The unit name
                        var unitName = reader.GetString(Db.Tables.UnitList.Col.UnitName);

                        resultList.Add(new Unit(unitId, unitName, Domain));
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the users added to the current business unit.
        /// </summary>
        /// <example>
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding users to the unit
        ///             unit.AddUser(domain.CurrentUser);
        /// 
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             
        ///             unit.AddUser(user);
        /// 
        ///             Console.WriteLine("The unit users:");
        /// 
        ///             foreach(var u in unit.Users)
        ///                 Console.WriteLine("\t{0}", u.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<User> Users
        {
            get
            {
                var resultList = new List<User>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1} where {2}=?",
                            Db.Tables.UserUnitList.Col.UserName,
                            Domain.GetObjectFullName(Db.Tables.UserUnitList.Name),
                            Db.Tables.UserUnitList.Col.UnitId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, GetRawId()));
                    },
                    reader => resultList.Add(new User(reader.GetString(Db.Tables.UserUnitList.Col.UserName), Domain)));

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the current unit id.
        /// </summary>
        public Guid Id { get; private set; }

        /// <summary>
        ///   Retrives the current unit id in the form of a byte array.
        /// </summary>
        /// <returns>Returns the current unit id in a form of a byte array.</returns>
        public byte[] GetRawId()
        {
            return Id.ToByteArray();
        }

        /// <summary>
        ///   Checks the existence of the unit.
        /// </summary>
        /// <returns>Returns true if the unit still exists; otherwise, returns false.</returns>
        public bool Exists()
        {
            var wasFound = false;

            _domain.DbAccess.ExecuteScalar(
                cmd =>
                    {
                        cmd.CommandText = string.Format("select count(*) from {0} where {1}=?",
                            _domain.GetObjectFullName(Db.Tables.UnitList.Name),
                            Db.Tables.UnitList.Col.UnitId);

                        cmd.Parameters.Add(_domain.DbAccess.CreateParameter("id", OdbcType.Binary, GetRawId()));
                    },
                result => wasFound = DbValue.GetInteger(result) != 0);

            return wasFound;
        }

        /// <summary>
        ///   Deletes the current business unit.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in
        /// order to execute this function.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no
        /// permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Defining the parent and child units
        ///             var childUnit = domain.AddUnit("child_unit");
        ///             var parentUnit = domain.AddUnit("parent_unit");
        /// 
        ///             unit.AddUnit(childUnit);
        ///             parentUnit.AddUnit(unit);
        /// 
        ///             //Adding the current user to the unit
        ///             unit.AddUser(domain.CurrentUser);
        /// 
        ///             Console.WriteLine("Child unit: parent unit count = {0}",
        ///                 childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: child unit count = {0}",
        ///                 parentUnit.ChildUnits.Count());
        ///             Console.WriteLine("Current user: unit count = {0}",
        ///                 domain.CurrentUser.Units.Count());
        /// 
        ///             Console.WriteLine("Deleting the unit");
        /// 
        ///             unit.Delete();
        /// 
        ///             Console.WriteLine("Child unit: parent unit count = {0}",
        ///                 childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: child unit count = {0}",
        ///                 parentUnit.ChildUnits.Count());
        ///             Console.WriteLine("Current user: unit count = {0}",
        ///                 domain.CurrentUser.Units.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void Delete()
        {
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?)}}",
                        Domain.GetObjectFullName(Db.Procedures.DeleteUnit));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_id", OdbcType.Binary, GetRawId()));
                });

            // Deleting the unit from the unit cache
            Domain.DeleteUnitFromCache(this);
        }


        /// <summary>
        ///   Adds the specified child unit to the current unit.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings
        /// in order to execute this function.</remarks>
        /// <param name="childUnit">The new child unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="childUnit"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a unit with the same id was already added to the current unit.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a cycle of units is formed.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to build a hierarchy of units.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a hierarchy of units
        ///             var unit_1 = domain.AddUnit("unit_1");
        ///             var unit_2 = domain.AddUnit("unit_2");
        ///             var unit_3 = domain.AddUnit("unit_3");
        /// 
        ///             unit_2.AddUnit(unit_1);
        ///             unit_3.AddUnit(unit_2);
        /// 
        ///             //Trying to build a cycle of units
        ///             try
        ///             {
        ///                 unit_1.AddUnit(unit_3);
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Building a cycle of units is forbidden");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>        
        public void AddUnit(Unit childUnit)
        {
            if (childUnit == null)
                throw new ArgumentNullException("childUnit");

            AddUnit(childUnit.Id);
        }


        /// <summary>
        ///   Adds a child unit with the specified id to the current unit.
        /// </summary>
        /// <param name="childUnitId">The id of the new child unit.</param>
        /// <remarks>
        ///   The current user has to have the permission to change security settings
        /// in order to perform this operation.
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when a unit with the same id was already added to the current unit.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a cycle of units is formed.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to build a hierarchy of units.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a hierarchy of units
        ///             var unit_1 = domain.AddUnit("unit_1");
        ///             var unit_2 = domain.AddUnit("unit_2");
        ///             var unit_3 = domain.AddUnit("unit_3");
        /// 
        ///             unit_2.AddUnit(unit_1.Id);
        ///             unit_3.AddUnit(unit_2.Id);
        /// 
        ///             //Trying to build a cycle of units
        ///             try
        ///             {
        ///                 unit_1.AddUnit(unit_3.Id);
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Building a cycle of units is forbidden");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void AddUnit(Guid childUnitId)
        {
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.AddUnitParent));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_id",
                        OdbcType.Binary, childUnitId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_parent_unit_id",
                        OdbcType.Binary, GetRawId()));
                });
        }


        /// <summary>
        ///   Removes the specified child unit from the current unit.
        /// </summary>
        /// <param name="childUnit">The specified child unit.</param>
        /// <remarks>
        ///   The current user has to have the permission to change
        /// security settings in order to execute this function.
        /// </remarks>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="childUnit"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no
        /// permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is no such
        /// parent unit of the current unit.</exception>
        /// <example>
        ///   The following example shows how to remove a child unit from another unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Building a hierarchy of units
        ///             var childUnit = domain.AddUnit("child_unit");
        ///             var parentUnit = domain.AddUnit("parent_unit");
        ///             
        ///             parentUnit.AddUnit(childUnit);
        /// 
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        /// 
        ///             Console.WriteLine("Removing the link between the parent and the child unit");
        /// 
        ///             parentUnit.RemoveUnit(childUnit);
        /// 
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RemoveUnit(Unit childUnit)
        {
            if (childUnit == null)
                throw new ArgumentNullException("childUnit");

            RemoveUnit(childUnit.Id);
        }


        /// <summary>
        ///   Removes a child unit with the specified id from the current unit.
        /// </summary>
        /// <param name="childUnitId">The id of the child unit.</param>
        /// <remarks>
        ///   The current user has to have the permission to change security
        /// permissions in order to execute this function.
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no
        /// permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is no such
        /// parent unit of the current unit.</exception>
        /// <example>
        ///   The following example shows how to remove a child unit from another unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Building a hierarchy of units
        ///             var childUnit = domain.AddUnit("child_unit");
        ///             var parentUnit = domain.AddUnit("parent_unit");
        ///             
        ///             parentUnit.AddUnit(childUnit);
        /// 
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        /// 
        ///             Console.WriteLine("Removing the link between the parent and the child unit");
        /// 
        ///             parentUnit.RemoveUnit(childUnit.Id);
        /// 
        ///             Console.WriteLine("Child unit: parent count = {0}", childUnit.Parents.Count());
        ///             Console.WriteLine("Parent unit: children count = {0}", parentUnit.ChildUnits.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RemoveUnit(Guid childUnitId)
        {
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.DeleteUnitParent));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_id",
                        OdbcType.Binary, childUnitId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_parent_unit_id",
                        OdbcType.Binary, GetRawId()));
                });
        }


        /// <summary>
        ///   Checks that the current unit contains the specified child unit.
        /// </summary>
        /// <param name="childUnit">The specified child unit.</param>
        /// <returns>Returns true if the current unit contains the specified child unit;
        /// otherwise, false.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="childUnit"/> is null.</exception>
        /// <example>
        ///   The following example shows how to check that a unit contains the specified child unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             // Creating unit hieararchy
        ///             var topUnit = domain.AddUnit("top_unit");
        ///             var childUnit = domain.AddUnit("child_unit");
        /// 
        ///             topUnit.AddUnit(childUnit);
        ///             
        ///             Console.WriteLine("'Top unit' contains 'Child unit' = {0}",
        ///                 topUnit.ContainsUnit(childUnit));
        ///             Console.WriteLine("'Child unit' contains 'Top unit' = {0}",
        ///                 childUnit.ContainsUnit(topUnit));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool ContainsUnit(Unit childUnit)
        {
            if (childUnit == null)
                throw new ArgumentNullException("childUnit");

            return ContainsUnit(childUnit.Id);
        }


        /// <summary>
        ///   Checks that the current unit contains a child unit with the specified id.
        /// </summary>
        /// <param name="childUnitId">The id of the child unit.</param>
        /// <returns>Returns true if the current unit contains a child unit with the specified id;
        /// otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check that a unit contains a child unit with the specified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             // Creating unit hieararchy
        ///             var topUnit = domain.AddUnit("top_unit");
        ///             var childUnit = domain.AddUnit("child_unit");
        /// 
        ///             topUnit.AddUnit(childUnit);
        ///             
        ///             Console.WriteLine("'Top unit' contains 'Child unit' = {0}",
        ///                 topUnit.ContainsUnit(childUnit.Id));
        ///             Console.WriteLine("'Child unit' contains 'Top unit' = {0}",
        ///                 childUnit.ContainsUnit(topUnit.Id));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool ContainsUnit(Guid childUnitId)
        {
            var roleWasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Tables.UnitParentList.Name),
                        Db.Tables.UnitParentList.Col.ParentUnitId,
                        Db.Tables.UnitParentList.Col.UnitId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("parent_unit_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, childUnitId.ToByteArray()));
                },
                result => roleWasFound = DbValue.GetInteger(result) == 1);

            return roleWasFound;
        }


        /// <summary>
        ///   Adds the specified user to the current unit.
        /// </summary>
        /// <remarks>The current user has to have the permission to change
        /// security settings in order to execute this function.</remarks>
        /// <param name = "user">The user to be added to the current unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission
        /// to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the specified user was
        /// already added to the current unit.</exception>
        /// <example> 
        ///   The following example shows how to add users to a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding users to the unit
        ///             unit.AddUser(domain.CurrentUser);
        /// 
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             unit.AddUser(user);
        /// 
        ///             Console.WriteLine("Unit users:");
        /// 
        ///             foreach(var u in unit.Users)
        ///                 Console.WriteLine("\t{0}", u.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void AddUser(IUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            AddUser(user.Name);
        }

        /// <summary>
        ///   Adds a user with the specified name to the current unit.
        /// </summary>
        /// <remarks>
        /// <para>The name of the user to be added to the current unit is lowered and trimmed.</para>
        /// <para>The current user has to have the permission to change
        /// security settings in order to execute this function.</para></remarks>
        /// <param name = "userName">The name of the user to be added to the current unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userName"/> is an empty string.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission
        /// to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the specified user was
        /// already added to the current unit.</exception>
        /// <example> 
        ///   The following example shows how to add users to a business unit.
        /// <code> 
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding users to the unit
        ///             unit.AddUser(domain.CurrentUser.Name);
        /// 
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        ///             unit.AddUser(user.Name);
        /// 
        ///             Console.WriteLine("Unit users:");
        /// 
        ///             foreach(var u in unit.Users)
        ///                 Console.WriteLine("\t{0}", u.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void AddUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            userName = userName.Trim().ToLower();

            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.AddUserUnit));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_user_name", OdbcType.NVarChar, userName));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_id", OdbcType.Binary, GetRawId()));
                });
        }

        /// <summary>
        ///   Checks that the current unit contains the specified user.
        /// </summary>
        /// <param name = "user">The specified user.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        /// <returns>Returns true if the current security role contains the specified user;
        /// otherwise, false.</returns>
        /// <example>
        ///    The following example shows how to check that a unit contains the specified user.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        ///          
        ///             //Creating a user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        /// 
        ///             //Adding the user to the unit
        ///             unit.AddUser(user);
        /// 
        ///             //Checking the unit users
        ///             Console.WriteLine("The unit contains 'my_user' = {0}", unit.ContainsUser(user));
        ///             Console.WriteLine("The unit contains the current user = {0}",
        ///                 unit.ContainsUser(domain.CurrentUser));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool ContainsUser(IUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            return ContainsUser(user.Name);
        }

        /// <summary>
        ///   Checks that the current unit contains a user with the specified name.
        /// </summary>
        /// <remarks>
        /// <para>The name of the user is lowered and trimmed.</para>
        /// </remarks>
        /// <param name = "userName">The name of the user.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userName"/> is an empty string.</exception>
        /// <returns>Returns true if the current security role contains a user with the specified name;
        /// otherwise, false.</returns>
        /// <example>
        ///    The following example shows how to check that a unit contains a user with the specified name.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a unit
        ///             var role = domain.AddUnit("my_unit");
        ///          
        ///             //Creating a user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        /// 
        ///             //Adding the user to the unit
        ///             unit.AddUser(user);
        /// 
        ///             //Checking the unit users
        ///             Console.WriteLine("The unit contains 'my_user' = {0}", unit.ContainsUser(user.Name));
        ///             Console.WriteLine("The unit contains the current user = {0}",
        ///                 unit.ContainsUser(domain.CurrentUser.Name));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool ContainsUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            userName = userName.Trim().ToLower();

            var userWasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Tables.UserUnitList.Name),
                        Db.Tables.UserUnitList.Col.UnitId,
                        Db.Tables.UserUnitList.Col.UserName);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("user_name", OdbcType.NVarChar, userName));
                },
                result => { userWasFound = DbValue.GetInteger(result) == 1; });


            return userWasFound;
        }

        /// <summary>
        ///   Deletes the specified user from the current unit.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings
        /// in order to execute this function.</remarks>
        /// <param name = "user">The user to be deleted from the current unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission
        /// to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current unit doesn't contain the
        /// specified user.</exception>
        /// <example>
        ///   The following example shows how to delete a user from a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding the current user to the unit
        ///             unit.AddUser(domain.CurrentUser);
        /// 
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        /// 
        ///             Console.WriteLine("Deleting the current user from the unit");
        /// 
        ///             unit.RemoveUser(domain.CurrentUser);
        /// 
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RemoveUser(IUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            RemoveUser(user.Name);
        }

        /// <summary>
        ///   Deletes a user with the specified name from the current unit.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings
        /// in order to execute this function.</remarks>
        /// <param name = "userName">The user to be deleted from the current unit.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userName"/> is an empty string.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission
        /// to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current unit doesn't contain the
        /// specified user.</exception>
        /// <example>
        ///   The following example shows how to delete a user from a business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///          
        ///             //Creating a unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding the current user to the unit
        ///             unit.AddUser(domain.CurrentUser);
        /// 
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        /// 
        ///             Console.WriteLine("Deleting the current user from the unit");
        /// 
        ///             unit.RemoveUser(domain.CurrentUser.Name);
        /// 
        ///             Console.WriteLine("Unit: user count = {0}", unit.Users.Count());
        ///             Console.WriteLine("Current user: unit count = {0}", domain.CurrentUser.Units.Count());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RemoveUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userName.Trim().Length == 0)
                throw new ArgumentException("User name cannot be empty.");

            userName = userName.Trim().ToLower();

            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.DeleteUserUnit));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_user_name", OdbcType.NVarChar, userName));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_id", OdbcType.Binary, GetRawId()));
                });
        }

        /// <summary>
        ///   Reads the current unit property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <returns>Returns the property value.</returns>
        private object ReadProperty(string propertyName)
        {
            object propertyValue = null;

            _domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        propertyName,
                        Domain.GetObjectFullName(Db.Tables.UnitList.Name),
                        Db.Tables.UnitList.Col.UnitId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, GetRawId()));
                },
                result => propertyValue = result);

            return propertyValue;
        }

        /// <summary>
        ///   Updates the current unit property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <param name = "propertyValue">The property value.</param>
        /// <param name = "propertyType">The property database type.</param>
        private void WriteProperty(string propertyName, object propertyValue, OdbcType propertyType)
        {
            var rowCount = _domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=? where {2}=?",
                        Domain.GetObjectFullName(Db.Tables.UnitList.Name),
                        propertyName,
                        Db.Tables.UnitList.Col.UnitId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("value", propertyType, DbValue.GetDbValue(propertyValue)));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("unit_id", OdbcType.Binary, GetRawId()));
                });

            if (rowCount < 1)
                throw new DbOperationException();
        }

        /// <summary>
        ///   Refreshes the properties of the current unit.
        /// </summary>
        public void Refresh()
        {
            _unitName = null;
            _displayName = null;
        }
    }
}